package edit

import "os"
import "panel"

import "bufio"
import "strings"
import "strconv"
import "io"
import "exec"
import "bytes"
import "⚛sdl"

func WriteOut(lines []string, fname string) {
    newline := []byte{'\n'}
    of, err := os.Create(fname)
    if err == nil { 
        defer of.Close()
        for _, line := range lines {
            // HORRID. Will fix. TODO
            of.WriteString( line )
            of.Write(newline)
        }
    } else {
        panel.Log("oops: ",err)
    }
}

type EditCompilePanel struct {
	panel.Common
	Edit *EditPanel
	mark panel.Type
}

type MarkPanel struct {
	panel.Common
	marks map[int]string
}

func (e *MarkPanel) Clear() {
	e.marks = nil
}

func (e *MarkPanel) Mark(line int, message string) {
	if e.marks == nil { e.marks = map[int]string{} }
	e.marks[line-1] = message
}

func (e *MarkPanel) FocusForClick(line, col int) panel.Type {
	if e.marks != nil && len(e.marks[line]) > 0 {
		e.Show("err: " + e.marks[line] + ".")		
	}
	e.Cursor(line, col)
	return e
}

func (mp *MarkPanel) Refresh() {
	m := mp.marks
	if m == nil { return }
	for line := 0; line < mp.Lines; line += 1 {
		s := m[line] 
		rune := ' '
		if len(s) > 0 { rune = '>' }
		mp.Put(line, 0, rune) 
		// RESTORE if len(s) > 0 { curses.Stdwin.ChAttr( 2, line + 1, 10, 0, int(3) ) }
	}
}

func (e *EditCompilePanel) Resize(lines, cols int, loff, coff int) {
	e.mark.Resize(lines, 1, loff, coff)
	e.Edit.Resize(lines, cols-1, loff, coff+1)
}

func (e *EditCompilePanel) FocusForClick(line, col int) panel.Type {
	if col == 0 { return e.mark.FocusForClick(line, col) }
	return e.Edit.FocusForClick(line, col-1)
}

func NewEditCompilePanel(target panel.Scrollable, other panel.Type) *EditCompilePanel {
	mark := &MarkPanel{}
	edit := NewEditPanel(target, other, mark)
	result := &EditCompilePanel{panel.Common{}, edit, mark}
	edit.Parent(result)
	mark.Parent(result)
	return result 
}

func (e *EditCompilePanel) Refresh() {
	panel.Log("EditCompilePanel.Refresh")
	e.Edit.Refresh()
	e.mark.Refresh()
}

// -----------------------------------------------------------------------------

type EditPanel struct {
    panel.Common
    target panel.Scrollable
    marks *MarkPanel	// marker panel for reports
    other panel.Type	// panel for ^G
    Line, Col int       // position within document
    scrollOffset int    // index of top line in window
    Content []string
}

func NewEditPanel(target panel.Scrollable, other panel.Type, marks *MarkPanel) *EditPanel {
    return &EditPanel{panel.Common: panel.Common{}, target: target, marks: marks, other: other, Content: []string{}}
}

func (e *EditPanel) Show(message string) {
	e.other.Show(message)
}

func (e *EditPanel) Focus() {
    e.Cursor(e.Line - e.scrollOffset, e.Col)
}

func (e *EditPanel) loadFile(fileName string) {
    panel.Log("loadFile '", fileName, "'")
    r, err := os.Open(fileName)
    if err != nil {
        panel.Log("BOOM -- ", err)
        return
    }
    defer r.Close()
    e.Content = []string{}
    br := bufio.NewReader( r )
    for {
        s, err := br.ReadString('\n')
        if err == os.EOF { break }
        e.Content = append(e.Content, s[0:len(s)-1])
    }
    e.Cursor(0,0)
    e.Refresh()
}

func (e *EditPanel) Do(command string) {
    fun, arg := panel.Chop(command)
    if fun == "r" { e.loadFile( strings.TrimSpace(arg) ) 
    } else 
        panel.Log("EditPanel.Do(", fun, " ", arg, ")")
}

func (e *EditPanel) Cursor(line, col int) {
    e.Common.Cursor(line, col)
    e.Line, e.Col = line + e.scrollOffset, col
}

func (e *EditPanel) FocusForClick(line, col int) panel.Type {
    e.Cursor(line, col)
    return e
}

func (e *EditPanel) Refresh() {
    for i := 0; i < panel.Min(len(e.Content), e.Lines); i += 1 {
        j := i + e.scrollOffset
        x := ""
        if j < len(e.Content) { x = e.Content[j] }
        e.PutString(i, 0, x)
        e.Clear(i, len(x))
    }
    e.Cursor(e.Line - e.scrollOffset, e.Col)
}

func (e *EditPanel) makeSpace() {
    for len(e.Content) < e.Line + 1 { e.Content = append(e.Content, "") }
    for len(e.Content[e.Line]) < e.Col + 1 { e.Content[e.Line] += " " }
}

func (e *EditPanel) Clear(line, col int) {
    for i := col; i < e.Cols; i += 1 { e.Put(line, i, ' ') }
}

func (e *EditPanel) Key(rune int) panel.Type {
    needsScrolling := false
    switch rune {

    case sdl.K_UP:
        if e.Line > e.scrollOffset {
            e.Line -= 1
        } else if e.scrollOffset > 0 {
            e.scrollOffset -= 1
            e.Line -= 1
            e.Refresh()
            needsScrolling = true
        } else {
        }

    case sdl.K_DOWN:
        if e.Line - e.scrollOffset < e.Lines - 1 {
            e.Line += 1
        } else {
            e.scrollOffset += 1
            e.Line += 1
            e.Refresh()
            needsScrolling = true
        }

    case sdl.K_LEFT:
        if e.Col > 0 { e.Col -= 1}

    case sdl.K_RIGHT:
        if e.Col < e.Cols - 1 { e.Col += 1}

    case sdl.K_PAGEUP:
        if e.Line == e.scrollOffset {
            e.Line -= e.Lines
            e.scrollOffset -= e.Lines
            if e.Line < 0 { e.Line, e.scrollOffset = 0, 0 }
            e.Refresh()
            needsScrolling = true
        } else {
            e.Line = e.scrollOffset
            needsScrolling = true
        }

    case sdl.K_PAGEDOWN:
        if e.Line - e.scrollOffset == e.Lines - 1 {
            // next screen
            e.Line += e.Lines
            e.scrollOffset += e.Lines
            e.Refresh()
            needsScrolling = true
        } else {
            // to bottom of screen
            e.Line = e.Lines + e.scrollOffset - 1
        }

    case sdl.K_DELETE:
        e.Col += 1
        fallthrough

    case sdl.K_BACKSPACE:
        if e.Line < len(e.Content) && e.Col < len(e.Content[e.Line]) && e.Col > 0 {
            x := e.Content[e.Line]
            updated := x[:e.Col - 1] + x[e.Col:]
            e.Content[e.Line] = updated
            e.Col -= 1
            e.PutString(e.Line - e.scrollOffset, 0, updated)
        }

    case sdl.K_RETURN: 
        if e.Line > len(e.Content) - 1 || e.Line >= e.Lines -1 { break }
        e.makeSpace()
        e.Content = append(e.Content, "")
        copy(e.Content[e.Line+2:], e.Content[e.Line+1:])
        e.Content[e.Line + 1] = e.Content[e.Line][e.Col:]
        e.Content[e.Line] = e.Content[e.Line][:e.Col]
        soff := e.scrollOffset
        for i := e.Line; i < panel.Min(len(e.Content), e.Lines); i += 1 {
            e.PutString(i - soff, 0, e.Content[i] )
            e.Clear(i - soff, len(e.Content[i]))
        }
        needsScrolling = true
        e.Line += 1
        e.Col = 0
        
    case 'd' - 'a' + 1:
    	reports := e.compile()
    	e.marks.Clear()
    	for _, report := range reports {
    		ln := report.LineNumber
    		iln, _ := strconv.Atoi(ln)
    		e.marks.Mark(iln, report.MessageText)
    	}
    	e.marks.Refresh()
    	
    case 'g' - 'a' + 1:
    	panel.Log("Someone said ^G! e.other = ", e.other)
    	if e.other != nil { return e.other }

    default:
        if rune < ' ' || rune > 126 || e.Col >= e.Cols - 1 {
            if rune > 0 { panel.Log("unhandled key ", rune) }
            break
        }
        e.makeSpace()
        character := string([]byte{byte(rune)})
        x := e.Content[e.Line]
        updated := x[:e.Col] + character + x[e.Col:] 
        e.Content[e.Line] = updated
        e.Col += 1
        e.PutString(e.Line - e.scrollOffset, 0, updated)

    }
    if needsScrolling && e.target != nil { 
        e.target.FillIn(e.Lines, len(e.Content), e.scrollOffset) 
    }
    e.Cursor(e.Line - e.scrollOffset, e.Col)
    return nil
}

type Report struct {
	FileName string
	LineNumber string
	MessageText string
}

func (e *EditPanel) compile() []Report {
	const cmd = "6g"
	var CMD = os.Getenv("GOBIN") + "/" + cmd
    panel.Log("doing 'compile' with 6g")
    const fname = "/tmp/compiling.go"
    WriteOut( e.Content, fname )
    out, err := exec.Run( CMD, []string{cmd, fname}, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout )
    if err != nil { panel.Log("oops: ", err); return nil }
    var buf bytes.Buffer
    io.Copy(&buf, out.Stdout)
    w, err := out.Wait(0)
    out.Close()
    panel.Log("output: ", string(buf.Bytes()))
    _ = w
//
	reports := []Report{}
	lines := strings.Split( string(buf.Bytes()), "\n", -1)
	for _, line := range lines {
		if len(line) > 0 && line[0] != '\t' {
			colon := strings.IndexRune(line, ':')
			space := strings.IndexRune(line, ' ')
			fileName := line[0:colon]
			lineNumber := line[colon+1:space-1]
			messageText := line[space+1:]
			reports = append(reports,Report{fileName, lineNumber, messageText}) 
		}
	}
	return reports
}

